/*****************************************************************************
 * Java Plug-in Framework (JPF)
 * Copyright (C) 2004-2007 Dmitry Olshansky
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *****************************************************************************/
package fr.umlv.main;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.java.plugin.ObjectFactory;
import org.java.plugin.Plugin;
import org.java.plugin.PluginManager;
import org.java.plugin.PluginManager.PluginLocation;
import org.java.plugin.registry.IntegrityCheckReport;
import org.java.plugin.registry.ManifestInfo;
import org.java.plugin.registry.ManifestProcessingException;
import org.java.plugin.registry.PluginRegistry;
import org.java.plugin.util.ExtendedProperties;
import org.java.plugin.util.IoUtil;
import org.java.plugin.util.ResourceManager;

// TODO: Auto-generated Javadoc
/**
 * The Class DefaultApplicationInitializer.
 */
public class DefaultApplicationInitializer implements ApplicationInitializer {
    
    /** The Constant PARAM_APPLICATION_PLUGIN. */
    protected static final String PARAM_APPLICATION_PLUGIN =
        "org.java.plugin.boot.applicationPlugin"; //$NON-NLS-1$
    
    /** The Constant PARAM_INTEGRITY_CHECK_MODE. */
    protected static final String PARAM_INTEGRITY_CHECK_MODE =
        "org.java.plugin.boot.integrityCheckMode"; //$NON-NLS-1$
    
    /** The Constant PARAM_PLUGINS_COLLECTOR. */
    protected static final String PARAM_PLUGINS_COLLECTOR =
        "org.java.plugin.boot.pluginsCollector"; //$NON-NLS-1$
    
    /** The Constant PARAM_PLUGINS_WHITE_LIST. */
    protected static final String PARAM_PLUGINS_WHITE_LIST =
        "org.java.plugin.boot.pluginsWhiteList"; //$NON-NLS-1$
    
    /** The Constant PARAM_PLUGINS_BLACK_LIST. */
    protected static final String PARAM_PLUGINS_BLACK_LIST =
        "org.java.plugin.boot.pluginsBlackList"; //$NON-NLS-1$

    /** The log. */
    private Log log;
    
    /** The config. */
    private ExtendedProperties config;
    
    /** The integrity check mode. */
    private String integrityCheckMode;
    
    /** The collector. */
    private PluginsCollector collector;
    
    /** The white list. */
    private Set<String> whiteList;
    
    /** The black list. */
    private Set<String> blackList;

    /* (non-Javadoc)
     * @see fr.umlv.main.ApplicationInitializer#configure(org.java.plugin.util.ExtendedProperties)
     */
    public void configure(final ExtendedProperties configuration)
            throws Exception {
        // Initializing logging system.
        String log4jConfigKey = "log4j.configuration"; //$NON-NLS-1$
        if (System.getProperty(log4jConfigKey) == null) {
            // Trying to find log4j configuration.
            if (configuration.containsKey(log4jConfigKey)) {
                System.setProperty(log4jConfigKey,
                        configuration.getProperty(log4jConfigKey));
            } else {
                File log4jConfig = new File(
                        configuration.getProperty("applicationRoot") //$NON-NLS-1$
                        + File.separator + "log4j.properties"); //$NON-NLS-1$
                if (!log4jConfig.isFile()) {
                    log4jConfig = new File(
                            configuration.getProperty("applicationRoot") //$NON-NLS-1$
                            + File.separator + "log4j.xml"); //$NON-NLS-1$
                }
                if (log4jConfig.isFile()) {
                    try {
                        System.setProperty(log4jConfigKey,
                                IoUtil.file2url(log4jConfig).toExternalForm());
                    } catch (MalformedURLException e) {
                        // ignore
                    }
                }
            }
        }
        log = LogFactory.getLog(getClass());
        log.info("logging system initialized"); //$NON-NLS-1$
        log.info("application root is " //$NON-NLS-1$
                + configuration.getProperty("applicationRoot")); //$NON-NLS-1$
        config = configuration;
        integrityCheckMode =
            configuration.getProperty(PARAM_INTEGRITY_CHECK_MODE, "full"); //$NON-NLS-1$
        collector = getCollectorInstance(
                configuration.getProperty(PARAM_PLUGINS_COLLECTOR));
        collector.configure(configuration);
        log.debug("plug-ins collector is " + collector); //$NON-NLS-1$
        try {
            whiteList = loadList(
                    configuration.getProperty(PARAM_PLUGINS_WHITE_LIST, null));
        } catch (IOException ioe) {
            log.warn("failed loading white list", ioe); //$NON-NLS-1$
        }
        if (whiteList != null) {
            log.debug("white list loaded"); //$NON-NLS-1$
        }
        try {
            blackList = loadList(
                    configuration.getProperty(PARAM_PLUGINS_BLACK_LIST, null));
        } catch (IOException ioe) {
            log.warn("failed loading black list", ioe); //$NON-NLS-1$
        }
        if (blackList != null) {
            log.debug("black list loaded"); //$NON-NLS-1$
        }
    }
    
    /**
     * Load list.
     *
     * @param location the location
     * @return the sets the
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private Set<String> loadList(final String location) throws IOException {
        if (location == null) {
            return null;
        }
        final Set<String> result = new HashSet<String>();
        BufferedReader reader = new BufferedReader(new InputStreamReader(
                new FileInputStream(location), "UTF-8")); //$NON-NLS-1$
        try {
            String line;
            while ((line = reader.readLine()) != null) {
                line = line.trim();
                if (line.length() > 0) {
                    result.add(line);
                }
            }
        } finally {
            reader.close();
        }
        log.debug("read " + result.size() //$NON-NLS-1$
                + " list items from " + location); //$NON-NLS-1$
        return result;
    }
    
    /**
     * Gets the collector instance.
     *
     * @param className the class name
     * @return the collector instance
     */
    private PluginsCollector getCollectorInstance(
            final String className) {
        if (className != null) {
            try {
                return (PluginsCollector) Class.forName(className)
                    .newInstance();
            } catch (InstantiationException ie) {
                log.warn("failed instantiating plug-ins collector " //$NON-NLS-1$
                        + className, ie);
            } catch (IllegalAccessException iae) {
                log.warn("failed instantiating plug-ins collector " //$NON-NLS-1$
                        + className, iae);
            } catch (ClassNotFoundException cnfe) {
                log.warn("failed instantiating plug-ins collector " //$NON-NLS-1$
                        + className, cnfe);
            }
        }
        return new DefaultPluginsCollector();
    }
    
    /* (non-Javadoc)
     * @see fr.umlv.main.ApplicationInitializer#initApplication(fr.umlv.main.BootErrorHandler, java.lang.String[])
     */
    public Application initApplication(final BootErrorHandler errorHandler,
            final String[] args) throws Exception {
        // Prepare parameters to start plug-in manager.
        log.debug("collecting plug-in locations"); //$NON-NLS-1$
        Collection<PluginLocation> pluginLocations = collector.collectPluginLocations();
        log.debug("collected " + pluginLocations.size() //$NON-NLS-1$
                + " plug-in locations, instantiating plug-in manager"); //$NON-NLS-1$
        // Create instance of plug-in manager.
        PluginManager pluginManager =
            ObjectFactory.newInstance(config).createManager();
        pluginLocations = filterPluginLocations(pluginManager.getRegistry(),
                pluginLocations);
        log.debug(pluginLocations.size() + " plug-in locations remain after " //$NON-NLS-1$
                + "applying filters, publishing plug-ins"); //$NON-NLS-1$
        // Publish discovered plug-in manifests and corresponding plug-in folders.
        pluginManager.publishPlugins(
                pluginLocations.toArray(
                        new PluginLocation[pluginLocations.size()]));
        if (!"off".equalsIgnoreCase(integrityCheckMode)) { //$NON-NLS-1$
            // Check plug-in's integrity.
            log.debug("checking plug-ins set integrity"); //$NON-NLS-1$
            IntegrityCheckReport integrityCheckReport =
                pluginManager.getRegistry().checkIntegrity(
                        "light".equalsIgnoreCase(integrityCheckMode) ? null //$NON-NLS-1$
                            : pluginManager.getPathResolver());
            log.info("integrity check done: errors - " //$NON-NLS-1$
                    + integrityCheckReport.countErrors() + ", warnings - " //$NON-NLS-1$
                    + integrityCheckReport.countWarnings());
            if (integrityCheckReport.countErrors() != 0) {
                // Something wrong in plug-ins set.
                log.info(integrityCheckReport2str(integrityCheckReport));
                if (!errorHandler.handleError(ResourceManager.getMessage(
                        Boot.PACKAGE_NAME, "integrityCheckFailed"), //$NON-NLS-1$
                        integrityCheckReport)) {
                    return null;
                }
            } else if (log.isDebugEnabled()
                    && ((integrityCheckReport.countErrors() > 0)
                    || (integrityCheckReport.countWarnings() > 0))) {
                log.debug(integrityCheckReport2str(integrityCheckReport));
            }
        }
        // application plug-in ID
        String appPluginId = config.getProperty(PARAM_APPLICATION_PLUGIN);
        log.info("application plug-in is " + appPluginId); //$NON-NLS-1$
        // get the start-up application plug-in
        Plugin appPlugin = pluginManager.getPlugin(appPluginId);
        log.debug("got application plug-in " + appPlugin //$NON-NLS-1$
                + ", initializing application"); //$NON-NLS-1$
        if (!(appPlugin instanceof ApplicationPlugin)) {
            log.error("application plug-in class " //$NON-NLS-1$
                    + appPlugin.getClass().getName()
                    + " doesn't assignable with " //$NON-NLS-1$
                    + ApplicationPlugin.class.getName());
            throw new ClassCastException(appPlugin.getClass().getName());
        }
        return ((ApplicationPlugin) appPlugin).initApplication(config.getSubset(appPluginId + "."), args); //$NON-NLS-1$
    }
    
    /**
     * Integrity check report2str.
     *
     * @param report the report
     * @return the string
     */
    protected String integrityCheckReport2str(final IntegrityCheckReport report) {
        StringBuilder buf = new StringBuilder();
        buf.append("integrity check report:\r\n"); //$NON-NLS-1$
        buf.append("-------------- REPORT BEGIN -----------------\r\n"); //$NON-NLS-1$
        for (IntegrityCheckReport.ReportItem item : report.getItems()) {
            buf.append("\tseverity=").append(item.getSeverity()) //$NON-NLS-1$
                .append("; code=").append(item.getCode()) //$NON-NLS-1$
                .append("; message=").append(item.getMessage()) //$NON-NLS-1$
                .append("; source=").append(item.getSource()) //$NON-NLS-1$
                .append("\r\n"); //$NON-NLS-1$
        }
        buf.append("-------------- REPORT END -----------------"); //$NON-NLS-1$
        return buf.toString();
    }

    /**
     * Filter plugin locations.
     *
     * @param registry the registry
     * @param pluginLocations the plugin locations
     * @return the collection
     * @throws ManifestProcessingException the manifest processing exception
     */
    protected Collection<PluginLocation> filterPluginLocations(final PluginRegistry registry,
            final Collection<PluginLocation> pluginLocations)
            throws ManifestProcessingException {
        if ((whiteList == null) && (blackList == null)) {
            return pluginLocations;
        }
        final List<PluginLocation> result = new LinkedList<PluginLocation>();
        for (PluginLocation pluginLocation : pluginLocations) {
            ManifestInfo manifestInfo = registry.readManifestInfo(
                    pluginLocation.getManifestLocation());
            if (whiteList != null) {
                if (isPluginInList(registry, manifestInfo, whiteList)) {
                    result.add(pluginLocation);
                }
                continue;
            }
            // blackList is not NULL here
            if (isPluginInList(registry, manifestInfo, blackList)) {
                continue;
            }
            result.add(pluginLocation);
        }
        return result;
    }
    
    /**
     * Checks if is plugin in list.
     *
     * @param registry the registry
     * @param manifestInfo the manifest info
     * @param list the list
     * @return true, if is plugin in list
     */
    private boolean isPluginInList(final PluginRegistry registry,
            final ManifestInfo manifestInfo, final Set<String> list) {
        if (list.contains(manifestInfo.getId())) {
            return true;
        }
        return list.contains(registry.makeUniqueId(manifestInfo.getId(),
                manifestInfo.getVersion()));
    }
}
